package cn.jinzhu.netty.protocol;

import cn.jinzhu.netty.message.LoginRequestMessage;
import cn.jinzhu.netty.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.ByteToMessageCodec;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.List;
import java.util.Objects;

/**
 * ### 自定义协议要素
 * <p>
 * * 魔数，用来在第一时间判定是否是无效数据包
 * * 版本号，可以支持协议的升级
 * * 序列化算法，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如：json、protobuf、hessian、jdk
 * * 指令类型，是登录、注册、单聊、群聊... 跟业务相关
 * * 请求序号，为了双工通信，提供异步能力
 * * 正文长度
 * * 消息正文
 */
@Slf4j
public class MessageCodec extends ByteToMessageCodec<Message> {


    /**
     * 转码ByteBuf
     *
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception ----自定义协议
     *                   <p>
     *                   出站                自定义字节总数加起来要是2的倍数 为了严谨
     */
    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        //1.魔数 用来在第一时间判定是否是无效数据包-6字节魔数
        out.writeBytes(new byte[]{5, 4, 9, 9});
        //2.版本号 不同版本解析不同
        out.writeByte(1);
        //3.序列化算法 目前采用jdk  1字节的序列化方式  jdk--0 json--1 目前定义jdk假设为0
        out.writeByte(0);
        //4.指令类型 指令类型 是否是聊天 或者群聊等等不同业务不同指令类型 type
        out.writeByte(msg.getMessageType());
        //5.请求序号 自定义4字节
        out.writeInt(msg.getSequenceId());
        //为了对齐长度 满足2的倍数
        out.writeByte(0xff);
        //6.消息正文  我们采用的jdk  获取内容的字节数；
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //将对象转换成2进账字节数组  序列化  msg -- 二进制
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(bos);
        objectOutputStream.writeObject(msg);
        byte[] bytes = bos.toByteArray();
        //7.正文长度 4字节
        out.writeInt(bytes.length);
        //8.写入内容
        out.writeBytes(bytes);
        log.info("自定义协议 转码");

    }

    /**
     * 解码msg
     * 解码 要看转码 按照顺序读取相应字节
     *
     * @param ctx
     * @param out
     * @throws Exception 入栈
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {


        //拿到转码第一个字节魔术 也就是四个字节
        int magicNum = in.readInt();
        //拿版本号1个字节
        byte version = in.readByte();
        //拿到序列号 1个字节
        byte serailizerType = in.readByte();
        //拿到指令类型 1个字节
        byte messageType = in.readByte();
        //拿到请求序号 四个字节
        int sequenceId = in.readInt();
        //拿到没用的0xff
        in.readByte();
        //拿消息正文成长度
        int length = in.readInt();
        //拿到消息正文实际长度进行解码读取
        byte[] bytes = new byte[length];
        //读取该字节长度 从索引0开始读到内容最大长度  ---内容
        in.readBytes(bytes, 0, length);
        //判断拿到的序列号类型是否是jdk 0 或者是json 1  进行反序列化

        if (serailizerType == 0) {

            //进行反序列化
            ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
            //转码是什么类型 解码就是什么类型 转码是message对象 解码也是message对象 要强制转换
            Message message = (Message) inputStream.readObject();
            out.add(message);
            log.info("自定义协议 解码 解码正文获取内容为{}", message);
        }
    }


    public static void main(String[] args) throws Exception {
        EmbeddedChannel channel = new EmbeddedChannel(new MessageCodec(), new LoggingHandler());
        LoginRequestMessage loginRequestMessage = new LoginRequestMessage("nihao ", "sada");
//        channel.writeOutbound(loginRequestMessage);
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
        new MessageCodec().encode(null,loginRequestMessage,buffer);
        channel.writeInbound(buffer);
    }
}
